package com.balloon.gateway.security.manager;

import com.balloon.gateway.enums.StatusCodeEnums;
import com.balloon.gateway.exception.TokenParseErrorException;
import com.balloon.gateway.redis.RedisUtils;
import com.balloon.gateway.utils.JwtUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.Claims;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

import java.util.Collection;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author liaofuxing
 * @version 1.0.0
 * @date 2022/3/8 11:12
 * @description JWT token 校验和续签管理
 */
@Component
@Primary
public class TokenAuthenticationManager implements ReactiveAuthenticationManager {
    private static final Logger log = LoggerFactory.getLogger(TokenAuthenticationManager.class);

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Override
    @SuppressWarnings("unchecked")
    public Mono<Authentication> authenticate(Authentication authentication) {
        try {
            RedisUtils redisUtils = new RedisUtils(stringRedisTemplate);
            Claims claimsJWT = JwtUtil.parseJWT(authentication.getPrincipal().toString());
            ObjectMapper mapper = new ObjectMapper();

            Map<String, String> userMap = mapper.readValue(claimsJWT.getSubject(), Map.class);
            String redisToken = redisUtils.get("SYSTEM_USER_INFO:" + userMap.get("username"));
            if(redisToken == null || !redisToken.equals(authentication.getPrincipal().toString())){
                log.error("token 过期或token 错误");
                return Mono.error(new TokenParseErrorException(StatusCodeEnums.TOKEN_PARSE_ERROR.getName(), String.valueOf(StatusCodeEnums.TOKEN_PARSE_ERROR.getCode())));
            }
            redisUtils.setEx("SYSTEM_USER_INFO:" + userMap.get("username"), authentication.getPrincipal().toString(), 30, TimeUnit.MINUTES);

            return Mono.just(authentication).map(auth -> claimsJWT).map(claims -> new UsernamePasswordAuthenticationToken(
                    claims.getSubject(),
                    null,
                    (Collection<? extends GrantedAuthority>) claims.get("roles")));

        } catch (Exception e) {
            e.printStackTrace();
            return Mono.error(new CredentialsExpiredException("用户凭证错误"));
        }
    }
}